/**
 * Copyright (c) 2006-2015 Hzins Ltd. All Rights Reserved.
 * <p>
 * This code is the confidential and proprietary information of
 * Hzins. You shall not disclose such Confidential Information
 * and shall use it only in accordance with the terms of the agreements
 * you entered into with Hzins,http://www.hzins.com.
 */
package com.huize.ladon.apm.common.util;

import com.huize.ladon.apm.common.domain.SerializerWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.ByteBuffer;

import static com.huize.ladon.apm.common.constant.MessageConstants.*;

/**
 * MQ消息编码/解码
 *
 * @author hz15051315
 * @date 2019年3月7日 下午2:14:02
 */
public final class SerializerUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(SerializerUtils.class);

    /**
     * 总长度8 = 协议头(长度3) + bodyLen位(长度4) + 消息类型位(长度1)
     */
    private static final int HEADER_LEN = STX_LENGTH + BODY_LENGTH + MSG_TYPE_LENGTH;

    /**
     * 总长度2 = CRC校验位(长度1) + 协议尾(长度1)
     */
    private static final int END_LEN = CRC_CHECK_LENGTH + ETX_LENGTH;

    /**
     * 编码
     * <p>
     * 协议头(长度3) + bodyLen(长度4) + 消息类型(长度1) + body(长度body.length) + CRC校验位(长度1) + 协议尾(长度1)
     * </p>
     *
     * @param wrapper 消息序列化包装类
     * @return
     */
    public static byte[] encode(SerializerWrapper wrapper) {
        byte msgType = wrapper.getMessageType();
        byte[] body = wrapper.getBody();
        int bodyLen = body.length;
        int bufferLength = HEADER_LEN + body.length + END_LEN;
        ByteBuffer buffer = ByteBuffer.allocate(bufferLength);

        buffer.put(ByteUtils.initBytes(STX_LENGTH, STX));                // (1)协议头标记，长度为3
        buffer.put(ByteUtils.intToBytes((bodyLen + 1), BODY_LENGTH));    // (2)data数据Length，长度为4
        buffer.put(msgType);                                             // (3)消息类型，长度为1
        buffer.put(body);                                                // (4)data数据，长度为data.length

        //签名算法:2位协议头~body全部内容
        byte[] tmp = buffer.array();
        SubtractTools st = new SubtractTools(tmp,1);
        tmp = st.getBytes(tmp.length - 3);
        int xor = ByteUtils.countXOR(tmp, tmp.length);
        tmp = null;
        st = null;

        buffer.put((byte)xor);                                			// (5)CRC循环校验位，长度为1
        buffer.put(ETX);                                                // (6)协议尾标记，长度为1

        byte[] result = buffer.array();
        buffer.clear();
        return result;
    }

    /**
     * 解码
     * <p>
     * 协议头(长度3) + bodyLen(长度4) + 消息类型(长度1) + body(长度body.length) + CRC校验位(长度1) + 协议尾(长度1)
     * </p>
     *
     * @param buffer
     * @return
     */
    public static SerializerWrapper decode(byte[] buffer) {

        int bufferLen = buffer.length;
        if (buffer == null || bufferLen <= 4) {
            LOGGER.warn("SerializerUtils.decode() param (byte[] buffer) invalid. buffer = {}", buffer);
            return null;
        }
        //协议解码
        byte[] data = protocolDecode(buffer);
        SubtractTools st = new SubtractTools(data,0);
        byte msgType = st.getByte();

        //组装返回字段
        SerializerWrapper wrapper = new SerializerWrapper(msgType, st.getBytes(data.length-1));
        return wrapper;
    }

    private static byte[] protocolDecode(byte[] data)
    {
        if(data[0] != STX || data[1] != STX || data[2] != STX)
            return null;

        //System.out.println(data.length);
        if(data[data.length-1] != ETX)
            return null;

        byte[] tmp = new byte[data.length - 3];
        SubtractTools st = new SubtractTools(data,1);
        tmp = st.getBytes(data.length - 3);
        if(ByteUtils.countXOR(tmp, tmp.length) != ByteUtils.byteToInt(data[data.length-2]))
            return null;

        st = new SubtractTools(data,3);
        int length = ByteUtils.bytesToInt(st.getBytes(4));
        return st.getBytes(length);
    }

    /**
     * 打印bytes数组
     */
    public static String printBytes(byte[] data) {
        if (data == null) {
            return "";
        }
        StringBuilder builder = new StringBuilder(500);
        for (byte buffer : data) {
            builder.append(buffer).append(",");
        }

        if (builder.length() > 0) {
            builder.append("\n");
        }
        return builder.toString();
    }

    public static void main(String[] args)
    {
        String str = "123abc";
        byte type = (byte)17;
        SerializerWrapper wrapper = new SerializerWrapper(type,str.getBytes());
        byte[] data = encode(wrapper);
        System.out.println(ByteUtils.binary2Hex(data));

        SerializerWrapper w2 = decode(data);
        System.out.println(w2.getMessageType());
        System.out.println(ByteUtils.binary2Hex(w2.getBody()));
    }
}
